
# One-Hot编码（虚拟变量）
import mglearn.datasets
import numpy as np
import pandas as pd
from IPython.core.display_functions import display
from matplotlib import pyplot as plt
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.feature_selection import SelectPercentile, SelectFromModel, RFE
from sklearn.linear_model import LogisticRegression, LinearRegression, Ridge
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder, PolynomialFeatures, MinMaxScaler
from sklearn.svm import SVR
from sklearn.datasets import load_diabetes, load_breast_cancer

# 分类变量
'''
我们以美国成年人收入数据集为例，它来自1994年的普查数据，任务是预测工人的收入是否超过50,000美元。
数据集包含年龄、雇佣方式（如独立经营、私营企业员工、政府职员等）、教育水平、性别、每周工作时长和职业等特征。

这是一个二分类任务，目标是判断收入是否超过50,000美元。虽然也可以预测具体收入（回归任务），但分类任务更简单，且研究50,000美元的分界线也很有意义。

数据集中，age 和 hours-per-week 是连续特征，而 workclass、education、gender 和 occupation 是分类特征，这些特征来自固定类别，表示定性属性。
'''

# One-Hot编码（虚拟变量）
'''
One-hot 编码（也叫虚拟变量或 N 取一编码）是处理分类变量的常用方法。它将一个分类变量替换为多个新特征，每个特征取值为 0 或 1。
例如，workclass 特征有 4 个可能的取值，通过 one-hot 编码会生成 4 个新特征，每个特征对应一个类别。如果某个类别被选中，对应的特征值为 1，其余为 0。
One-hot 编码与统计学中的虚拟编码相似，但略有不同。统计学中通常将 k 个类别的特征编码为 k-1 个特征，以避免数据矩阵秩亏，简化分析。

'''

# 下面使用pandas来将数据转换为分类变量的 one-hot 编码

# 文件中没有包含列名称的表头，因此我们传入header=None
# 然后在"names"中显式地提供列名称
data = pd.read_csv(
 "../data/adult.data", header=None, index_col=False,
 names=['age', 'workclass', 'fnlwgt', 'education', 'education-num',
 'marital-status', 'occupation', 'relationship', 'race', 'gender',
 'capital-gain', 'capital-loss', 'hours-per-week', 'native-country',
 'income'])
# 为了便于说明，我们只选了其中几列
data = data[['age', 'workclass', 'education', 'gender', 'hours-per-week',
 'occupation', 'income']]
# IPython.display可以在Jupyter notebook中输出漂亮的格式
display(data.head())

'''
1. 检查字符串编码的分类数据
在处理字符串编码的分类数据时，建议先检查每列是否包含有意义的分类数据。
特别是对于人工输入的数据（如网站用户填写的信息），可能存在拼写错误或大小写不一致的情况，因此需要预处理。
例如，性别可能被填写为“male”或“man”，但我们需要将它们归为同一类别。
检查列内容的一个好方法是使用 Pandas Series 的 value_counts() 方法，它可以显示唯一值及其出现次数。
'''

print(data.gender.value_counts())

'''
get_dummies() 函数会将这些列转换为 one-hot 编码形式，即为每个类别创建一个新的列，列值为 0 或 1，表示数据点是否属于该类别
'''

print("Original features:\n", list(data.columns), "\n")
data_dummies = pd.get_dummies(data)
print("Features after get_dummies:\n", list(data_dummies.columns))
# Features after get_dummies:
#  ['age', 'hours-per-week', 'workclass_ ?', 'workclass_ Federal-gov', 'workclass_ Local-gov', 'workclass_ Never-worked', 'workclass_ Private', 'workclass_ Self-emp-inc', 'workclass_ Self-emp-not-inc',
#  'workclass_ State-gov', 'workclass_ Without-pay', 'education_ 10th', 'education_ 11th', 'education_ 12th', 'education_ 1st-4th', 'education_ 5th-6th', 'education_ 7th-8th', 'education_ 9th', 'education_ Assoc-acdm',
#  'education_ Assoc-voc', 'education_ Bachelors', 'education_ Doctorate', 'education_ HS-grad', 'education_ Masters', 'education_ Preschool', 'education_ Prof-school', 'education_ Some-college', 'gender_ Female', 'gender_ Male',
#  'occupation_ ?', 'occupation_ Adm-clerical', 'occupation_ Armed-Forces', 'occupation_ Craft-repair', 'occupation_ Exec-managerial', 'occupation_ Farming-fishing', 'occupation_ Handlers-cleaners',
#  'occupation_ Machine-op-inspct', 'occupation_ Other-service', 'occupation_ Priv-house-serv', 'occupation_ Prof-specialty', 'occupation_ Protective-serv', 'occupation_ Sales', 'occupation_ Tech-support',
#  'occupation_ Transport-moving', 'income_ <=50K', 'income_ >50K']

# 可以看到，连续特征 age 和 hours-per-week 没有发生变化，而分类特征的每个可能取值
# 都被扩展为一个新特征

'''
可以使用 values 属性将 data_dummies DataFrame 转换为 NumPy 数组，用于训练机器学习模型。
但在训练之前，需要将目标变量（如 income 列）从数据中分离出来，避免将其包含在特征中，因为这是监督学习中常见的错误。
'''

features = data_dummies.loc[:, ['age', 'occupation_ Transport-moving']]
X = features.values
y = data_dummies['income_ >50K'].values
print("X.shape: {} y.shape: {}".format(X.shape, y.shape))

# 现在数据的表示方式可以被 scikit-learn 处理，我们可以像之前一样继续下一步
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=0)
logreg = LogisticRegression()
logreg.fit(X_train,y_train)
print("Test score: {:.2f}".format(logreg.score(X_test, y_test)))

'''
在这个例子中，对包含训练数据和测试数据的整个数据框调用 get_dummies 是很重要的。
这样可以确保训练集和测试集中分类变量的表示方式完全一致，避免因列名称不同而导致语义不一致的问题。

'''

# 数字可以编码分类变量
'''
在 adult 数据集中，分类变量通常以字符串形式存储，这虽然可能带来拼写错误，但明确标识了变量是分类的。
然而，分类变量也可能被编码为整数，例如在问卷调查中，workclass 可能被记录为 0、1、2 等数字，而不是字符串（如 "Private"）。
这种情况下，数据列包含数字（如 0 到 8），很难直观判断它是连续变量还是分类变量。
但如果知道这些数字代表不同的就业状态，那么它们显然是分类变量，不能当作连续变量处理。
'''

'''
分类特征通常用整数编码，但它们不应被视为连续特征。整数特征是否被视为连续或离散（one-hot 编码）有时并不明确。
如果编码的类别之间没有顺序关系（如 workclass），则必须将其视为离散特征并进行 one-hot 编码。
而对于有顺序关系的特征（如五星评分），选择哪种编码方式取决于具体任务、数据以及所使用的机器学习算法。
'''

'''
pandas.get_dummies 函数默认将所有数字视为连续变量，不会对其进行 one-hot 编码。
如果需要对数字分类变量进行 one-hot 编码，可以使用 scikit-learn 的 OneHotEncoder，它可以明确指定哪些变量是分类的。
另一种方法是将数据框中的数值列转换为字符串类型，这样 get_dummies 会将其视为分类变量。
为了说明这一点，可以创建一个包含两列的 DataFrame，一列是字符串，另一列是整数。
'''

# 创建一个DataFrame，包含一个整数特征和一个分类字符串特征
demo_df = pd.DataFrame({'Integer Feature': [0,1,2,1],'Categorical Feature': ['socks','fox','socks','box']})
display(demo_df)

# 使用 get_dummies 只会编码字符串特征，不会改变整数特征
pd.get_dummies(demo_df)
display(pd.get_dummies(demo_df))

# 如果你想为“Integer Feature”这一列创建虚拟变量，可以使用 columns 参数显式地给出想
# 要编码的列。于是两个特征都会被当作分类特征处理

demo_df['Integer Feature'] = demo_df['Integer Feature'].astype(str)
pd.get_dummies(demo_df,columns=['Integer Feature','Categorical Feature'])
display(pd.get_dummies(demo_df,columns=['Integer Feature','Categorical Feature']))
